本文为看雪论坛优秀文章
本次分析的重点是,用实例来展示异常的机制的原理以及IDA的一些用法,借用此crack来演示。刚好,这个程序使用的技术和windows异常机制有关,也许程序的作者也是想表达出这样的意思。在这里对windows异常机制做一些补充,在我前面的文章中提到了CPU异常以及用户异常,这里补充一下VEH→SEH→UEH以及VCH,网络上有这类的资料有很多,在这就不在细讲,只做简单的总结。
VEH向量化异常处理器、SEH结构化异常处理器,UEH(UEF)顶层异常处理器。
VEH:保存在全局的一个链表中,所有的线程共享一个VEH链表,VEH活动于用户层。
SEH:保存在栈空间上是一个局部的链表,每个线程都有自己的SEH,线程之间的SEH互相不影响(既活动于内核层又活动于用户层)。
UEH:保存在一个全局变量上,由于全局变量是所有线程能够共享的,所以所有的线程共享一个UEH,它是属于处理异常的最后一道关卡,如果没记错的话,它存在于两个地方,程序的开始位置和最后各存在一个,但是一般不做异常处理(也可以做异常处理),更多的被用于内存转储并上传服务器。最重要的是当程序处于被调试状态时,不会调用UEH。所以可以用于反调试。
VCH:位置是跟随SEH的,就存储在SEH的旁边的一个链表中,可以设置多个,线程共享。可以看成是SEH的一部分,因为存在于SEH后面。也不会做异常处理,前面异常处理成功了,才会调用此VCH。
a. KiUserExceptionDispatcher 函数
(1) 调用RtlDispatchException查找并执行异常处理函数;
(2) 如果RtlDispatchException返回真,调用ZwContinue再次进入0环重新把寄存器的值赋值给Trap_Frame,但线程再次返回3环时,会从修正后的位置开始行。
(3) 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发。
图为做小实验而找到的KiUserExceptionDispatcher:b. RtlDispatchException函数:
(1) 查找VEH链表(全局链表),如果有则调用。
(2) 查找SEH链表(局部链表,在堆栈中),如果有则调用。
(3) 最后一道防线UEH(UEF),住在SEH隔壁。
c. UnhandledExceptionFilter的执行流程:
(1) 通过NtQueryInformationProcess查询当前进程是否正在被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH,此时会进入第二轮分发。
(2) 如果没有被调试:
(a) 查询是否通过SetUnhandledExceptionFilter注册处理函数如果有就调用。(b) 如果没有通过SetUnhandledExceptionFilter注册处理函数弹出窗口让用户选择终止程序还是启动即时调试器。(c)如果用户没有启用即时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLE。用一张图表示执行顺序:
程序使用的是UEH的机制,安装一个顶层异常处理函数(SetUnhandledExceptionFilter),来处理异常,使用基于UEH机制的反调试技术。使用IDA pro打开发现基本都是使用的库函数。除了DialogFunc回调函数的功能还不知道,那么接下来分析主要功能,就在这个回调函数里面。
修改之后一目了然,并且在0040102B的地址处,可以发现正是消息回调函数,然后和10h进行比较,那么就可以确定10h其实是个枚举值,可以看到还有两个也是这枚举值,一并修改了,当然了如果想改回去也可以使用u 取消目标(局部变量\全局变量\函数)的定义。可以确定这是一个窗口消息,那么就可以搜索WM_就能搜索出来了。如果不确定是否是不是一个窗口消息,那么可以通过此链接搜索对应的16进制,一般常用的消息都能搜到。https://www.cnblogs.com/lihuali/p/7566989.html
可以看到返回的消息不是关闭程序的宏之后,就会执行右侧的指令,那么在40103E处和WM_COMMAND值相等时,就会执行左侧的代码,发现又在处和0BBBh比较,目前还不知道,wParam参数表示什么,我们使用OD查看一下。
发现是一个按钮事件,那么LOWORD(wParam)就表示是一个子窗口ID,那么在上面截图中,在40105E位置处发现压入了一个0BB8的值,通过连系下图就可知,是一个编辑框事件。
那么GetDlgItemTextA获取文本框的值,会返回一个编辑框中字符的个数和5比较。
那输入fubar看看能不能显示成功呢,发现还是错误。但逻辑是这样的没错。如果ESI和EDI比较成功会显示一些信息。不会弹出messagebox。
根据地址用OD动态调试看看401066,F8单步走,EIP没错,得到了输入的字符串在40332a地址处,继续F8,401068这里应该会产生一个异常,当然如果仅仅只是想暴力破解它,那么就可以把在此处把401068地址的数据直接NOP掉,这里就不做演示了,因为我们的目的并不在此。OD默认设置是忽略异常的,可以通过取消设置也会断下,也可以使用x64debug,我们使用x64debug查看。在左下角处,发现触发了异常。那么我们查看一下内存布局,发现当前是处于代码段,权限只是执行和读取,并不能写入。在这里啰嗦一句,OD中查看权限是不准确的,可能会显示读写执行,所以就换成x64debug,当然也可以使用内核调试器windebug来调试。继续F8执行,执行到77D8702D处时,x64debug会触发两次内存异常一直反复横跳,根本无法调试。那么我们设置一下点选项-异常-点Unknown exceptions,点不暂停(do not break),继续执行程序发现已经崩溃了,那么就可以确定了,UEH异常回调函数根本就没处理异常信息。这里你可能会说,OD调试为什么就不会崩溃,因为OD调试器有反反调试的插件,已经HOOK了关键的函数了,所以可以在OD里面去调试,这里就不再演示x64debug了(可能以后有所补充,毕竟要写内核级的驱动插件,目前还不会)。
发现一模一样,那么就可以确定,进入了R3环的KiUserExceptionDispatcher函数。
第一个调用的就是RtlDispatchException这个函数。分析到这,动态调试基本就已经没啥可看的了,具体的执行流程可参照前面所补充的一些知识。前面有所提到这样的一张图,程序一开始就初始化安装了顶层异常函数。那么我们可以点击401103地址处进入这个回调函数中进行静态的分析。
可以在Stuctures结构选项中insert插入顶层异常指针所用的结构体,TopLevelExceptionFilter结构体的原型。通过FN+INSERT点击Add standard structure再CTRL+F进行搜索添加。通过对403367处T进行context解析,发现edi的值被修改了,指向40304E处的值magic。可以看到在40336D地址处eax的值发生了变化,eax=40332A,又把40332A赋值给了ebx+esi,在这里不清楚到底是哪一个寄存器,那么我们就可以通过windebug附加此程序,直接找到context结构的A0位置处。
那么可以确定的是在403372处,context.esi被修改了。eax做了加法运算,eax=40107F,并且把eax赋值给了context.eip,最后eax进行异或运算eax=0,在自减1,最后得出eax=-1,那么作为一个返回值返回宏为EXCEPTION_CONTINUE_EXECUTION,回到异常块eip处再执行一次。由于context.eip被修改那么将会从指定eip开始执行。那么跟踪修改后的esi=40332A和edi=40304E可知:
看雪ID:APT_华生
https://bbs.pediy.com/user-home-919377.htm
*本文由看雪论坛 APT_华生 原创,转载请注明来自看雪社区。
《安卓高级研修班》2021年6月班火热招生中!